#include "../kernel.h"

/* This file implements a simple Inter Process Synchronizing API by
 * the proc wait/notify interface */

volatile struct ips_node_t *ips_nodes;
volatile struct ips_ap_t *ips_aps;

static volatile int ips_ap_free_head;
static spin_lock_t ips_ap_alloc_lock;

/* Internal routine to put ips into queue, return the new head */
static int ips_enqueue(int ips, int head);
/* Internal routine to pull ips from queue, return the new head */
static int ips_dequeue(int ips, int head);
/* Internal routine to pull the head from queue, return the new head */
static int ips_dequeue_head(int *head_ips, int head);

int
ips_init(void)
{
	 sysconf.ips_per_proc = PROCESS_IPS_COUNT;
	 if (ips_nodes == NULL || ips_aps == NULL)
	 {
		  kprintf("ips is not enabled, PTR is not initialized\n");
		  return -E_UNSUPPORTED;
	 }
	 
	 int count = 1 << (PROCESS_MAX_COUNT_SHIFT + PROCESS_IPS_COUNT_SHIFT);
	 int i;
	 for (i = 0; i != count; ++ i)
	 {
		  ips_nodes[i].status = IPS_FREE;
		  ips_nodes[i].object = IPS_OBJ_NULL;
		  ips_nodes[i].prev =
			   ips_nodes[i].next = i;
	 }

	 for (i = 0; i != IPS_AP_COUNT; ++ i)
	 {
		  ips_aps[i].free = 1;
		  ips_aps[i].next = i + 1;
	 }
	 ips_ap_free_head = 0;
	 spl_init(&ips_ap_alloc_lock);
	 
	 return 0;
}


int
ips_ap_alloc(int recv_level, int send_level)
{
	 int result;
	 spl_acquire(&ips_ap_alloc_lock);
	 result = ips_ap_free_head;
	 if (result != IPS_AP_COUNT)
	 {
		  ips_ap_free_head = ips_aps[result].next;
		  spl_release(&ips_ap_alloc_lock);
		  
		  spl_init(&ips_aps[result].lock);
		  
		  ips_aps[result].wait  = AP_WAIT_EMPTY;
		  ips_aps[result].queue = IPS_NULL;

		  ips_aps[result].recv_level = recv_level;
		  ips_aps[result].send_level = send_level;

		  ips_aps[result].free = 0;
		  
		  return result;
	 }
	 else
	 {
		  spl_release(&ips_ap_alloc_lock);
		  return -E_NO_RES;
	 }
}

void
ips_ap_free(int ap)
{
	 spl_acquire(&ips_aps[ap].lock);
	 if (ips_aps[ap].free)
	 {
		  spl_release(&ips_aps[ap].lock);
		  return;
	 }
	 ips_aps[ap].free = 1;
	 spl_release(&ips_aps[ap].lock);

	 spl_acquire(&ips_ap_alloc_lock);
	 if (!ips_aps[ap].free)
	 {
		  ips_aps[ap].next = ips_ap_free_head;
		  ips_ap_free_head  = ap;
	 }
	 spl_release(&ips_ap_alloc_lock);
}

/* ============================================================ */

int
ips_recv(int ap, int ips)
{
	 int pid = cur_proc_id();
	 if (ips_nodes[ips].object != IPS_OBJ_NULL)
		  return -E_BUSY;

	 ips_nodes[ips].object = ap;

	 spl_acquire(&ips_aps[ap].lock);
	 switch (ips_aps[ap].wait)
	 {
	 case AP_WAIT_EMPTY:
	 {
	 _WAIT_EMPTY:
		  ips_aps[ap].queue = ips_enqueue(ips, IPS_NULL);
		  ips_aps[ap].wait  = AP_WAIT_RECV;
		  
		  ips_nodes[ips].status = IPS_RECV_WAIT;

		  spl_release(&ips_aps[ap].lock);
		  return -E_AGAIN;
	 }
	 
	 case AP_WAIT_SEND:
	 {
		  int sender;
		  if ((ips_aps[ap].queue =
			   ips_dequeue_head(&sender, ips_aps[ap].queue)) == IPS_NULL)
		  {
			   ips_aps[ap].wait = AP_WAIT_EMPTY;
			   if (sender == IPS_NULL) goto _WAIT_EMPTY;
		  }
		  
		  ips_nodes[ips].status   = IPS_RECV_FINISHED;
		  ips_nodes[ips].payload  = ips_nodes[sender].payload;
		  
		  ips_nodes[sender].status = IPS_SEND_FINISHED;
		  spl_release(&ips_aps[ap].lock);

		  sch_notify(IPS_PROC(sender), WLEVEL_IPS);
		  // kprintf("notify %d\n", IPS_PROC(sender));

		  return 0;
	 }

	 case AP_WAIT_REQUEST:
	 {
		  ips_nodes[ips].status   = IPS_RECV_FINISHED;
		  ips_nodes[ips].payload  = ips_aps[ap].queue;
		  
		  ips_aps[ap].queue = 0;
		  ips_aps[ap].wait = AP_WAIT_EMPTY;
		  
		  spl_release(&ips_aps[ap].lock);
		  return 0;
	 }

	 case AP_WAIT_RECV:
	 {
		  ips_aps[ap].queue = ips_enqueue(ips, ips_aps[ap].queue);
		  
		  ips_nodes[ips].status = IPS_RECV_WAIT;
		  
		  spl_release(&ips_aps[ap].lock);
		  return -E_AGAIN;
	 }
	 default:
		  kprintf("PANIC when recv status %d\n", ips_aps[ap].wait);
		  monitor(NULL);
		  return -E_UNSUPPORTED;
	 }
}

int
ips_recv_try(int ips, uint64_t *payload, int wait)
{	 
	 int ap = ips_nodes[ips].object;
	 if (ap == IPS_OBJ_NULL)
		  return -E_INVAL;

	 int result;
	 
	 spl_acquire(&ips_aps[ap].lock);
	 switch (ips_nodes[ips].status)
	 {
	 case IPS_RECV_WAIT:
		  switch (wait)
		  {
		  case IPS_WAIT:
			   sch_wait(WLEVEL_IPS);
			   spl_release(&ips_aps[ap].lock);
			   call_yield();
			   break;
			   
		  case IPS_WAIT_INT:
			   sch_wait_try();
			   spl_release(&ips_aps[ap].lock);
			   call_yield();
			   break;

		  default:
			   spl_release(&ips_aps[ap].lock);
		  }

		  return -E_AGAIN;
		  
	 case IPS_RECV_FINISHED:
		  *payload = ips_nodes[ips].payload;
		  ips_nodes[ips].status = IPS_FREE;
		  result = 0;
		  break;

	 case IPS_RECV_BREAKED:
		  result = -E_INTERRUPTED;
		  ips_nodes[ips].status = IPS_FREE;
		  break;
		  
	 default:
		  result = -E_INVAL;
		  break;
	 }

	 spl_release(&ips_aps[ap].lock);
	 ips_nodes[ips].object = IPS_OBJ_NULL;

	 return result;
}

int
ips_recv_break(int ips)
{
	 int ap = ips_nodes[ips].object;
	 if (ap == IPS_OBJ_NULL)
		  return -E_INVAL;

	 int result;
	 spl_acquire(&ips_aps[ap].lock);
	 if (ips_nodes[ips].status == IPS_RECV_WAIT)
	 {
		  if ((ips_aps[ap].queue =
			   ips_dequeue(ips, ips_aps[ap].queue)) == IPS_NULL)
		  {
			   ips_aps[ap].wait = AP_WAIT_EMPTY;
		  }

		  ips_nodes[ips].status = IPS_RECV_BREAKED;
		  result = 0;
	 }
	 else
	 {
		  result = -E_INVAL;
	 }
	 
	 spl_release(&ips_aps[ap].lock);
	 return result;
}

/* ============================================================ */

int
ips_request(int ap)
{
	 spl_acquire(&ips_aps[ap].lock);
	 switch (ips_aps[ap].wait)
	 {
	 case AP_WAIT_EMPTY:
	 {
	 _WAIT_EMPTY:
		  ips_aps[ap].queue = 1;
		  ips_aps[ap].wait  = AP_WAIT_REQUEST;

		  spl_release(&ips_aps[ap].lock);
		  return 0;
	 }
	 
	 case AP_WAIT_REQUEST:
	 {
		  ++ ips_aps[ap].queue;
		  spl_release(&ips_aps[ap].lock);
		  return 0;
	 }
	 
	 case AP_WAIT_RECV:
	 {
		  int recver = ips_aps[ap].queue;
		  if ((ips_aps[ap].queue =
			   ips_dequeue_head(&recver, ips_aps[ap].queue)) == IPS_NULL)
		  {
			   ips_aps[ap].wait = AP_WAIT_EMPTY;
			   if (recver == IPS_NULL) goto _WAIT_EMPTY;
		  }

		  ips_nodes[recver].status  = IPS_RECV_FINISHED;
		  ips_nodes[recver].payload = 1;

		  spl_release(&ips_aps[ap].lock);
		  
		  sch_notify(IPS_PROC(recver), WLEVEL_IPS);
		  // kprintf("notify %d\n", IPS_PROC(recver));
		  
		  return 0;
	 }
	 default:
		  kprintf("PANIC when call\n");
		  monitor(NULL);
		  
		  return -E_UNSUPPORTED;
	 }
}

/* ============================================================ */

int
ips_send(int ap, int ips, uint64_t payload)
{
	 if (ips_nodes[ips].object != IPS_OBJ_NULL)
		  return -E_BUSY;
	 ips_nodes[ips].payload = payload;
	 ips_nodes[ips].object  = ap;
	 
	 spl_acquire(&ips_aps[ap].lock);
	 switch (ips_aps[ap].wait)
	 {
	 case AP_WAIT_EMPTY:
	 {
	 _WAIT_EMPTY:
		  ips_aps[ap].queue = ips_enqueue(ips, IPS_NULL);
		  ips_aps[ap].wait  = AP_WAIT_SEND;

		  ips_nodes[ips].status = IPS_SEND_WAIT;
		  spl_release(&ips_aps[ap].lock);
		  return 0;
	 }
	 
	 case AP_WAIT_SEND:
	 {
		  ips_aps[ap].queue = ips_enqueue(ips, ips_aps[ap].queue);
		  
		  ips_nodes[ips].status = IPS_SEND_WAIT;
		  spl_release(&ips_aps[ap].lock);
		  return 0;
	 }
	 
	 case AP_WAIT_RECV:
	 {
		  int recver;
		  if ((ips_aps[ap].queue =
			   ips_dequeue_head(&recver, ips_aps[ap].queue)) == IPS_NULL)
		  {
			   ips_aps[ap].wait = AP_WAIT_EMPTY;
			   if (recver == IPS_NULL) goto _WAIT_EMPTY;
		  }

		  ips_nodes[ips].status  = IPS_SEND_FINISHED;
		  
		  ips_nodes[recver].status  = IPS_RECV_FINISHED;
		  ips_nodes[recver].payload = payload;

		  spl_release(&ips_aps[ap].lock);
		  sch_notify(IPS_PROC(recver), WLEVEL_IPS);
		  // kprintf("notify %d\n", IPS_PROC(recver));
		  
		  return 0;
	 }
	 default:
		  kprintf("PANIC when call\n");
		  monitor(NULL);
		  
		  return -E_UNSUPPORTED;
	 }
}

int
ips_send_try(int ips, int wait)
{
	 int ap = ips_nodes[ips].object;
	 if (ap == IPS_OBJ_NULL)
		  return -E_INVAL;

	 int result;
	 spl_acquire(&ips_aps[ap].lock);
	 switch (ips_nodes[ips].status)
	 {
	 case IPS_SEND_WAIT:
		  switch (wait)
		  {
		  case IPS_WAIT:
			   sch_wait(WLEVEL_IPS);
			   spl_release(&ips_aps[ap].lock);
			   call_yield();
			   break;
			   
		  case IPS_WAIT_INT:
			   sch_wait_try();
			   spl_release(&ips_aps[ap].lock);
			   call_yield();
			   break;

		  default:
			   spl_release(&ips_aps[ap].lock);
		  }

		  return -E_AGAIN;

	 case IPS_SEND_BREAKED:
		  result = -E_INTERRUPTED;
		  ips_nodes[ips].status = IPS_FREE;
		  break;

	 case IPS_SEND_FINISHED:
		  result = 0;
		  ips_nodes[ips].status = IPS_FREE;
		  break;

	 default:
		  result = -E_INVAL;
		  break;
	 }

	 spl_release(&ips_aps[ap].lock);
	 ips_nodes[ips].object = IPS_OBJ_NULL;
	 
	 return result;
}

int
ips_send_break(int ips)
{
	 int ap;
	 if ((ap = ips_nodes[ips].object) == IPS_AP_COUNT)
		  return -E_INVAL;

	 int result;
	 spl_acquire(&ips_aps[ap].lock);
	 if (ips_nodes[ips].status == IPS_SEND_WAIT)
	 {
		  if ((ips_aps[ap].queue =
			   ips_dequeue(ips, ips_aps[ap].queue)) == IPS_NULL)
		  {
			   ips_aps[ap].wait = AP_WAIT_EMPTY;
		  }

		  ips_nodes[ips].status = IPS_SEND_BREAKED;
		  result = 0;
	 }
	 else
	 {
		  result = -E_INVAL;
	 }
	 
	 spl_release(&ips_aps[ap].lock);
	 return result;
}

/* INTERNAL ROUTINES ========================================== */

static int
ips_enqueue(int ips, int head)
{
	 if (head == IPS_NULL)
	 {
		  ips_nodes[ips].prev =
			   ips_nodes[ips].next =
			   ips;
		  return ips;
	 }
	 else
	 {
		  ips_nodes[ips].next = head;
		  ips_nodes[ips].prev = ips_nodes[head].prev;
		  ips_nodes[ips_nodes[ips].next].prev = ips;
		  ips_nodes[ips_nodes[ips].prev].next = ips;
		  return head;
	 }
}

static int
ips_dequeue(int ips, int head)
{
	 if (ips_nodes[ips].object != ips_nodes[head].object)
		  return head;
	 
	 int prev = ips_nodes[ips].prev;
	 int next = ips_nodes[ips].next;
	 
	 ips_nodes[next].prev = prev;
	 ips_nodes[prev].next = next;

	 if (head != ips)
		  return head;
	 else if (next == ips)
		  return IPS_NULL;
	 else return next;
}

static int
ips_dequeue_head(int *head_ips, int head)
{
	 while (1)
	 {		  
		  while (1)
		  {
			   if (head == IPS_NULL)
			   {
					*head_ips = IPS_NULL;
					return IPS_NULL;
			   }
					  
			   if ((procs[IPS_PROC(head)].flags & PFLAG_VALID) &&
				   (procs[IPS_PROC(head)].sch_status != PSCH_DETACHED))
			   {
					*head_ips = head;
					break;
			   }
			   
			   head = ips_dequeue(head, head);
		  }

		  spl_acquire(&procs[IPS_PROC(head)].lock);
		  if ((procs[IPS_PROC(head)].flags & PFLAG_VALID) &&
			  (procs[IPS_PROC(head)].sch_status != PSCH_DETACHED))
		  {
			   spl_release(&procs[IPS_PROC(head)].lock);
			   return ips_dequeue(head, head);
		  }
		  else
		  {
			   spl_release(&procs[IPS_PROC(head)].lock);
			   head = ips_dequeue(head, head);
		  }
	 }
}
